调试 Common Lisp代码
Table of Contents
Common Lisp 标准是允许自定义调试代码的,在 Slime 中触发异常后会自动调用它自己的调试器 SLDB。
现代的 Common Lisp 实现都对函数编译时做了高质量的优化,比如 SBCL 默认情况下会对函数进行编译,编译后的代码会影响调试。因此,我们在调试代码的时候不能使用优化编译过的函数。有两种方法:
一个是全局执行,这样每个函数都不会被优化:
(proclaim '(optimize (debug 3)))
二是在函数内使用 declare:
(defun test () (declare (optimize (debug 3))) ...)
这时我们再对函数进行编译,就可以在 REPL 中调试了。注意,如果要调试的函数在执行上面代码之前就编译了的话,需要重新编译一次。
Common Lisp 提供了 break 函数可以便于我们下断点,如:
(defun test1 () (declare (optimize (debug 3))) (break) ;; 下断点 (loop for i from 0 to 10 do (print i)))
当代码执行到断点代码时,会触发 SLDB。
SLDB 最常用的几个快捷键:
- s 单步运行下一个表达式
- c Continue
- v 在源码中显示出当前执行的表达式
- q 终止调试
详细的请见 Slime 中文文档:http://slime-user-manual-cn.readthedocs.org/en/latest/chapter-4.html
1. step 宏
step 宏可以单步执行表达式,虽然 Common Lisp 标准定义了 step 函数,但并不是每个 CL 都实现了该函数;像 SBCL 对函数编译做了优化,执行步骤也不是预期的。Lispworks 和 CLISP 中,step 宏很好用。
在 SBCL 中,需要在函数内用 declare 把 debug 等级调高,重新编译,再调用 step:
(defun string-find (main-str str) (declare (optimize (debug 3) (speed 0))) (when (> (length main-str) 0) (search str main-str)))
然后在 REPL 中单步执行:
(step (string-find "hello" "o"))
2. trace 宏
用来跟踪函数调用,定义如下:
(trace &rest specs)
比如定义一个斐波那契数列:
(defun fab (n) (cond ((equal 0 n) 0) ((equal 1 n) 1) (t (+ (fab (- n 1)) (fab (- n 2))))))
然后对 fab 函数进行调用跟踪:
(trace fab)
调用 trace 函数后再执行它,就可以看到函数调用过程:
(fab 5) ;; 输出: ;; 0: (FAB 5) ;; 1: (FAB 4) ;; 2: (FAB 3) ;; 3: (FAB 2) ;; 4: (FAB 1) ;; 4: FAB returned 1 ;; 4: (FAB 0) ;; 4: FAB returned 0 ;; 3: FAB returned 1 ;; 3: (FAB 1) ;; 3: FAB returned 1 ;; 2: FAB returned 2 ;; 2: (FAB 2) ;; 3: (FAB 1) ;; 3: FAB returned 1 ;; 3: (FAB 0) ;; 3: FAB returned 0 ;; 2: FAB returned 1 ;; 1: FAB returned 3 ;; 1: (FAB 3) ;; 2: (FAB 2) ;; 3: (FAB 1) ;; 3: FAB returned 1 ;; 3: (FAB 0) ;; 3: FAB returned 0 ;; 2: FAB returned 1 ;; 2: (FAB 1) ;; 2: FAB returned 1 ;; 1: FAB returned 2 ;; 0: FAB returned 5 ;; 5
trace 函数如果不跟参数,列出当前被 trace 过的函数:
(trace) ; => (FAB)
用 untrace 函数可以解除跟踪。